﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace YoutubeDownloader
{
    public partial class Downloader : Form
    {
        private readonly string _path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        private Setting _setting;
        private DownloadItem _currentDownload;

        private readonly AutoResetEvent _eventDownload = new AutoResetEvent(false);
        private readonly List<Video> _actionsDownloader;
        private readonly ReaderWriterLockSlim _actionsDownloaderLocker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        
        private const int QueuesPeriod = 5000;

        public Downloader()
        {
            InitializeComponent();

            _setting = Setting.Deserialize(Path.Combine(_path, "Setting.xml"));

            _actionsDownloader = new List<Video>();
            ThreadPool.UnsafeRegisterWaitForSingleObject(_eventDownload, ActionsQueueDownload, false, QueuesPeriod, true);
        }
        
        private void DownloaderLoad(object sender, EventArgs e)
        {
            ProcessSearch();
        }
        
        private void AddVideoToListView(Video v)
        {
            if (resultsList.InvokeRequired)
            {
                resultsList.BeginInvoke(new Action<Video>(AddVideoToListView), v);
                return;
            }


            if (resultsList.Items.Cast<ListViewItem>().Any(listViewItem => ((Video)listViewItem.Tag).Id == v.Id))
            {
                return;
            }


            ListViewItem lvItem = new ListViewItem(v.Uploaded.ToShortDateString() + " - " + v.Uploaded.ToShortTimeString()) { ToolTipText = v.Description };

            if (resultsList.Groups[v.Uploader.ToLower()] == null)
            {
                resultsList.Groups.Add(v.Uploader.ToLower(), "Author: " + v.Uploader);
            }
            lvItem.Group = resultsList.Groups[v.Uploader.ToLower()];
            lvItem.SubItems.Add(v.Title);
            lvItem.SubItems.Add(v.Viewcount.ToString());
            lvItem.SubItems.Add(string.Empty);
            lvItem.SubItems.Add(string.Empty);
            lvItem.Tag = v;

            if (_setting.DownloadedVideoId != null && _setting.DownloadedVideoId.Any(id => id == v.Id))
            {
                lvItem.ForeColor = SystemColors.GrayText;
                lvItem.ImageKey = "checkmark";
            }

            resultsList.Items.Add(lvItem);
            //ProgressBar progressBar = new ProgressBar() {Minimum = 0, Maximum = 100, Name =v.Id};
            //resultsList.AddEmbeddedControl(progressBar, 3, lvItem.Index);
        }

        private void AddDownloadItem(DownloadItem downloadItem)
        {
            if (resultsList.InvokeRequired)
            {
                resultsList.BeginInvoke(new Action<DownloadItem>(AddDownloadItem), downloadItem);
                return;
            }

            
            foreach (ListViewItem item in resultsList.Items)
            {
                if (((Video)item.Tag).Id == downloadItem.Video.Id)
                {
                    switch (downloadItem.Event)
                    {
                        case DownloadItem.DownloadEvent.DownloadQueued:
                            item.ImageKey = "sand";
                            item.SubItems[4].Text = downloadItem.ToString();
                            break;
                        case DownloadItem.DownloadEvent.DownloadStarted:
                            item.ImageKey = "download";
                            item.SubItems[4].Text = downloadItem.ToString();
                            ProgressBar progressBar = new ProgressBar() { Minimum = 0, Maximum = 100, Name = downloadItem.Video.Id };
                            resultsList.AddEmbeddedControl(progressBar, 3, item.Index);
                            break;
                        case DownloadItem.DownloadEvent.DownloadFailed:
                            item.ImageKey = "round_delete";
                            item.SubItems[4].Text = downloadItem.ToString();
                            resultsList.RemoveEmbeddedControl(((ProgressBar)resultsList.GetEmbeddedControl(3, item.Index)));
                            break;
                        case DownloadItem.DownloadEvent.DownloadEnded:
                            item.ForeColor = SystemColors.GrayText;
                            item.ImageKey = "checkmark";
                            item.SubItems[4].Text = downloadItem.ToString();
                            resultsList.RemoveEmbeddedControl(((ProgressBar)resultsList.GetEmbeddedControl(3, item.Index)));
                            _setting.DownloadedVideoId.Add(downloadItem.Video.Id);
                            break;
                        case DownloadItem.DownloadEvent.Downloading:
                            item.ImageKey = "download";
                            ((ProgressBar) resultsList.GetEmbeddedControl(3, item.Index)).Value = downloadItem.Progress;
                            item.SubItems[4].Text = downloadItem.ToString();
                            break;
                    }
                    break;
                }
            }
        
        }

        #region Download
        private void ActionsQueueDownload(object ctx, bool timedout)
        {
            List<Video> actionToRemove = new List<Video>();
            List<Video> actionToDo = new List<Video>();
            //Consume Actions stack:
            try
            {
                _actionsDownloaderLocker.EnterReadLock();
                actionToDo = new List<Video>(_actionsDownloader);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Failed to add Video to the download queue: " + ex);
            }
            finally
            {
                _actionsDownloaderLocker.ExitReadLock();
            }

            foreach (Video video in actionToDo)
            {
                try
                {
                    VideoLink videoLink = video.FindBestVideoLink(_setting.FormatPreferred);

                    DownloadItem downloadItem = new DownloadItem();
                    downloadItem.Video = video;
                    downloadItem.VideoLink = videoLink;
                    downloadItem.StartTime = System.Environment.TickCount;
                    downloadItem.Progress = 0;
                    downloadItem.DownloadedByte = 0;
                    downloadItem.Event = DownloadItem.DownloadEvent.DownloadStarted;
                    _currentDownload = downloadItem;

                    if (videoLink == null)
                    {
                        downloadItem.Event = DownloadItem.DownloadEvent.DownloadFailed;
                        AddDownloadItem(downloadItem);
                    }
                    else
                    {
                        AddDownloadItem(downloadItem);

                        // Create an instance of WebClient
                        WebClient client = new WebClient();

                        // Hookup DownloadFileCompleted Event
                        client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ClientDownloadProgressChanged);

                        string filePath = "[" + videoLink.FormatDescription + "] " + video.Uploaded.ToShortDateString() + " - " + video.Title + ".mp4";
                        //\ / : * ? " < > |
                        filePath = filePath.Replace(@"\", @".");
                        filePath = filePath.Replace(@"/", @".");
                        filePath = filePath.Replace(@":", @".");
                        filePath = filePath.Replace(@"*", @".");
                        filePath = filePath.Replace(@"?", @".");
                        filePath = filePath.Replace("\"", @".");
                        filePath = filePath.Replace(@"<", @".");
                        filePath = filePath.Replace(@">", @".");
                        filePath = filePath.Replace(@"|", @".");

                        string downloadPath = Path.Combine(_path, "Downloads");
                        if (Directory.Exists(downloadPath) == false)
                        {
                            Directory.CreateDirectory(downloadPath);
                        }

                        string fileDest = Path.Combine(downloadPath, video.Uploader);
                        if (Directory.Exists(fileDest) == false)
                        {
                            Directory.CreateDirectory(fileDest);
                        }

                        string fileTempDest = Path.Combine(downloadPath, "Temp");
                        if (Directory.Exists(fileTempDest) == false)
                        {
                            Directory.CreateDirectory(fileTempDest);
                        }

                        fileDest = Path.Combine(fileDest, filePath);
                        fileTempDest = Path.Combine(fileTempDest, filePath);

                        // Start the download and copy the file to c:\temp
                        client.DownloadFileAsync(new Uri(videoLink.Link), fileTempDest);

                        while (client.IsBusy == true)
                        {
                            Thread.Sleep(100);
                        }

                        File.Move(fileTempDest, fileDest);

                        downloadItem.Event = DownloadItem.DownloadEvent.DownloadEnded;
                        AddDownloadItem(downloadItem);
                    }

                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Failed to download Video from the download queue: " + ex);
                }
                actionToRemove.Add(video);
            }

            try
            {
                _actionsDownloaderLocker.EnterWriteLock();
                foreach (Video action in actionToRemove)
                {
                    _actionsDownloader.Remove(action);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Failed to remove Video from the download queue: " + ex);
            }
            finally
            {
                _actionsDownloaderLocker.ExitWriteLock();
            }


            //
            //  start the consumer off waiting again
            ThreadPool.UnsafeRegisterWaitForSingleObject(_eventDownload, ActionsQueueDownload, false, QueuesPeriod, true);
        }

        void ClientDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            _currentDownload.DownloadedByte = e.BytesReceived;
            _currentDownload.TotalByte = e.TotalBytesToReceive;
            _currentDownload.Progress = e.ProgressPercentage;
            _currentDownload.Event = DownloadItem.DownloadEvent.Downloading;
            AddDownloadItem(_currentDownload);
        }

        public bool PostDownloadAction(Video v)
        {
            bool rVal = true;
            try
            {
                _actionsDownloaderLocker.EnterWriteLock();
                DownloadItem downloadItem = new DownloadItem
                                                {
                                                    Video = v,
                                                    Event = DownloadItem.DownloadEvent.DownloadQueued
                                                };

                AddDownloadItem(downloadItem);
                _actionsDownloader.Add(v);
                _eventDownload.Set();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Failed to Post Download Action: " + ex);
            }
            finally
            {
                _actionsDownloaderLocker.ExitWriteLock();
            }
            return rVal;
        }
        #endregion

        #region Search
        public void ProcessSearch()
        {
            if (searchBackgroundWorker.IsBusy)
            {
                MessageBox.Show("Youtube Downloader is already refreshing.");
            }
            else
            {
                searchBackgroundWorker.RunWorkerAsync();
            }
        }

        private void BackroungSearch(object sender, DoWorkEventArgs e)
        {
            SetWaitingForm(false);

            new Finder().Search(_setting.Authors, _setting.CrawlPeriodUrlParameter, SearchProgress);

            SetWaitingForm(true);
        }

        void SearchProgress(Finder.SearchStep step, object param1, object param2)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new Action<Finder.SearchStep, object, object>(SearchProgress), step, param1, param2);
                return;
            }
            switch (step)
            {
                case Finder.SearchStep.Started:
                    if (string.IsNullOrEmpty((string)param1) == false)
                    {
                        string searchKeyword = (string)param1;
                        if (resultsList.Groups[searchKeyword.ToLower()] == null)
                        {
                            resultsList.Groups.Add(searchKeyword, "Author: " + searchKeyword);
                        }
                    }
                    break;
                case Finder.SearchStep.DownloadingPageStarted:
                    break;
                case Finder.SearchStep.DownloadingPageEnded:
                    break;
                case Finder.SearchStep.ParsingPage:
                    break;
                case Finder.SearchStep.ItemAdded:
                    {
                        Video v = param1 as Video;
                        int p = (int)param2;
                        SetProgress(p);
                        AddVideoToListView(v);
                    }
                    break;
                case Finder.SearchStep.Ended:
                    SortList(0, true);

                    break;
            }
        }
        #endregion

        #region UI Callback
        private void SortList(int colIndex, bool forceDown = false)
        {
            if (this.InvokeRequired)
            {
                BeginInvoke(new Action<int, bool>(SortList), colIndex, forceDown);
                return;
            }
            //
            // Set the ListViewItemSorter property to a new ListViewItemComparer 
            // object. Setting this property immediately sorts the 
            // ListView using the ListViewItemComparer object.
            bool sortAsc;
            if (forceDown)
            {
                sortAsc = false;
                resultsList.Columns[colIndex].Tag = null;
            }
            else
            {
                if (resultsList.Columns[colIndex].Tag == null)
                {
                    sortAsc = true;
                    resultsList.Columns[colIndex].Tag = true;
                }
                else
                {
                    sortAsc = false;
                    resultsList.Columns[colIndex].Tag = null;
                }
            }
            this.resultsList.ListViewItemSorter = new ListViewItemComparer(colIndex, sortAsc);

            this.resultsList.SetGroupState(ListViewGroupState.Collapsible);
        }
        private void SetWaitingForm(bool ended = false)
        {
            if (this.InvokeRequired)
            {
                BeginInvoke(new Action<bool>(SetWaitingForm), ended);
                return;
            }

            if (ended)
            {
                this.Cursor = Cursors.Arrow;
                this.resultsList.EndUpdate();
                SetProgress(100);
            }
            else
            {
                resultsList.Items.Clear();
                this.Cursor = Cursors.AppStarting;
                this.resultsList.BeginUpdate();
                SetProgress(0);
            }
        }

        private void SetProgress(int p)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new Action<int>(SetProgress), p);
                return;
            }

            if (p >= 0 && p <= 100)
            {
                toolProgressBar.Value = p;
            }
        }

        private void AddToDownloadListClick(object sender, EventArgs e)
        {
            if (resultsList.SelectedItems.Count >= 1)
            {
                foreach (ListViewItem selectedItem in resultsList.SelectedItems)
                {
                    if (selectedItem.Tag is Video)
                    {
                        Video v = selectedItem.Tag as Video;

                        PostDownloadAction(v);
                    }
                }
            }
        }

        private void RefreshClick(object sender, EventArgs e)
        {
            ProcessSearch();
        }

        private void QuitClick(object sender, EventArgs e)
        {
            Close();
        }

        private void AboutClick(object sender, EventArgs e)
        {
            MessageBox.Show("Youtube Podcast Version 1.0\r\n\r\nhttp://ytpodcast.codeplex.com/", "About", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void SettingClick(object sender, EventArgs e)
        {
            Settings settingForm = new Settings(_setting);

            if (settingForm.ShowDialog() == DialogResult.OK)
            {
                _setting = settingForm.Setting;
                _setting.Serialize(Path.Combine(_path, "Setting.xml"));
            }
        }

        private void DownloaderClosed(object sender, FormClosedEventArgs e)
        {
            _setting.Serialize(Path.Combine(_path, "Setting.xml"));
        }

        private void WatchInExternalPlayerClick(object sender, EventArgs e)
        {
            if (resultsList.SelectedItems.Count >= 1)
            {
                foreach (ListViewItem selectedItem in resultsList.SelectedItems)
                {
                    if (selectedItem.Tag is Video)
                    {
                        Video v = selectedItem.Tag as Video;

                        VideoLink videoLink = v.FindBestVideoLink(_setting.FormatPreferred);

                        if (string.IsNullOrWhiteSpace(videoLink.Link) == false && string.IsNullOrWhiteSpace(_setting.MediaPlayerPath) == false)
                        {
                            ProcessStartInfo psInfo = new ProcessStartInfo
                                                          {
                                                              FileName = _setting.MediaPlayerPath,
                                                              Arguments = videoLink.Link
                                                          };

                            Process.Start(psInfo);
                        }
                        else
                        {
                            MessageBox.Show("Media Player path is not set, or URL can't be retrieved.");
                        }

                        break;
                    }
                }
            }
        }

        private void MarkAsDownloadedClick(object sender, EventArgs e)
        {
            if(resultsList.SelectedItems.Count > 0)
            {
                resultsList.SelectedItems[0].ForeColor = SystemColors.GrayText;
                _setting.DownloadedVideoId.Add(resultsList.SelectedItems[0].Text);
            }
        }

        private void MarkAsNotDownloadedClick(object sender, EventArgs e)
        {
            if (resultsList.SelectedItems.Count > 0)
            {
                resultsList.SelectedItems[0].ForeColor = SystemColors.WindowText;
                _setting.DownloadedVideoId.Remove(resultsList.SelectedItems[0].Text);
            }
        }

        private void OpenDownloadFolderClick(object sender, EventArgs e)
        {
            ProcessStartInfo processStartInfo = new ProcessStartInfo
                                                    {
                                                        Arguments = Path.Combine(_path, "Downloads"),
                                                        FileName = "explorer.exe"
                                                    };
            Process.Start(processStartInfo);
        }
        #endregion
    }

    
    // Implements the manual sorting of items by columns.
    class ListViewItemComparer : IComparer
    {
        private readonly int _col;
        private readonly bool _sortAsc;
        public ListViewItemComparer()
        {
            _col = 0;
            _sortAsc = true;
        }
        public ListViewItemComparer(int column, bool sortAsc)
        {
            _sortAsc = sortAsc;
            _col = column;
        }
        public int Compare(object x, object y)
        {
            Video a = ((ListViewItem)x).Tag as Video;
            Video b = ((ListViewItem)y).Tag as Video;
            switch (_col)
            {
                case 1:
                    return String.Compare(a.Id, b.Id)*(_sortAsc ? 1 : -1);
                case 0:
                    return DateTime.Compare(a.Uploaded, b.Uploaded) * (_sortAsc ? 1 : -1);
                case 2:
                    return (a.Viewcount - b.Viewcount) * (_sortAsc ? 1 : -1);
                default:
                    return String.Compare(((ListViewItem)x).SubItems[_col].Text, ((ListViewItem)y).SubItems[_col].Text) * (_sortAsc ? 1 : -1);

            }
        }
    }
}
